/*
 * Anything: A flexible and easy to use data structure that combines the 
 * characteristics of a list and a hashmap into one data structure.
 * 
 * Based on a paper by Peter Sommerlad and Marcel Rueedi on 
 * 'Do-it-yourself reflection' entered at EuroPLOP 1998.
 * 
 * This library was written completely from scratch and is based only 
 * in spirit on the original Java Anything implementation written by 
 * Andre Weinand/Marcel Rueedi in 1997/99 and enhanced by Stefan Tramm 
 * in 2006.
 * 
 * Copyright (c) 2008 Thomas Marti and others. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package any.functor.interfaces;

import static any.functor.Functions.asBigInteger;
import static any.functor.Functions.asByte;
import static any.functor.Functions.asDouble;
import static any.functor.Functions.asFloat;
import static any.functor.Functions.asInt;
import static any.functor.Functions.asLong;
import static any.functor.Functions.asShort;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Map.Entry;

import any.ImmutableAny;
import any.functor.impl.pred.BaseFunctionTypedPredicate;

public class ExactNumberPredicateBuilder {
	
	private static abstract class ExactNumberBasePredicate<T, A> extends BaseFunctionTypedPredicate<T, A> {
		ExactNumberBasePredicate(String description, TargetTypedFunction<ImmutableAny, T> asFunction) {
			super(description, asFunction);
		}
		public final boolean apply(Entry<String, ? extends ImmutableAny> entry) {
			BigDecimal value = entry.getValue().asBigDecimal(null);
			if (value == null) return false;
			try {
				return checkExactNumber(value);
			} catch (ArithmeticException e) {
				return false;
			}
		}
		protected abstract boolean checkExactNumber(BigDecimal any) throws ArithmeticException;
	}
	
	public PrimitiveFunctionTypedPredicate<Byte, byte[]> Byte() {
		return new ExactNumberBasePredicate<Byte, byte[]>("isExactByte", asByte()) {
			@Override protected boolean checkExactNumber(BigDecimal value) throws ArithmeticException {
				value.byteValueExact(); // throws ArithmeticException
				return true;
			}
		};
	}

	
	public PrimitiveFunctionTypedPredicate<Short, short[]> Short() {
		return new ExactNumberBasePredicate<Short, short[]>("isExactShort", asShort()) {
			@Override protected boolean checkExactNumber(BigDecimal value) throws ArithmeticException {
				value.shortValueExact(); // throws ArithmeticException
				return true;
			}
		};
	}
	
	public PrimitiveFunctionTypedPredicate<Integer, int[]> Int() {
		return new ExactNumberBasePredicate<Integer, int[]>("isExactInt", asInt()) {
			@Override protected boolean checkExactNumber(BigDecimal value) throws ArithmeticException {
				value.intValueExact(); // throws ArithmeticException
				return true;
			}
		};
	}
	
	public PrimitiveFunctionTypedPredicate<Long, long[]> Long() {
		return new ExactNumberBasePredicate<Long, long[]>("isExactLong", asLong()) {
			@Override protected boolean checkExactNumber(BigDecimal value) throws ArithmeticException {
				value.longValueExact(); // throws ArithmeticException
				return true;
			}
		};
	}
	
	public PrimitiveFunctionTypedPredicate<Float, float[]> Float() {
		return new ExactNumberBasePredicate<Float, float[]>("isExactFloat", asFloat()) { 
			@Override protected boolean checkExactNumber(BigDecimal value) throws ArithmeticException {
				return value.equals(BigDecimal.valueOf(value.floatValue()));
			}
		};
	}

	public PrimitiveFunctionTypedPredicate<Double, double[]> Double() {
		return new ExactNumberBasePredicate<Double, double[]>("isExactDouble", asDouble()) {
			@Override protected boolean checkExactNumber(BigDecimal value) throws ArithmeticException {
				return value.equals(BigDecimal.valueOf(value.doubleValue()));
			}
		};	
	}
	
	public FunctionTypedPredicate<BigInteger> BigInteger() {
		return new ExactNumberBasePredicate<BigInteger, Void>("isExactBigInteger", asBigInteger()) {
			@Override protected boolean checkExactNumber(BigDecimal value) throws ArithmeticException {
				value.toBigIntegerExact(); // throws ArithmeticException
				return true;
			}
		};	
	}
}
